/*
    kOTF Open Trace Format - a library for generating execution traces.
    Copyright 2012 Daniel K. O.

    kOTF is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    kOTF is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with kOTF.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "SessionImpl.hpp"

#include <assert.h>

#include <kotf/Session.hpp>
#include <kotf/Stream.hpp>
#include "Writer.hpp"
#include "StreamImpl.hpp"

extern const char * __progname;

namespace kotf {

    const char * SessionImpl::magic = "# KOTF 1.0.0";


    namespace {
        inline uint64_t get_timestamp()
        {
            struct timespec ts;
            clock_gettime(CLOCK_REALTIME, &ts);
            return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
        }
    }



    SessionImpl::SessionImpl(Session* s, const string& sname) :
        iface(s),
        name(sname),
        nextStreamId(1),
        nextStateId(1),
        nextCounterId(1),
        nextChannelId(1),
        startTime(get_timestamp()),
        enabled(true)
    {}


    SessionImpl::~SessionImpl()
    {
        if (enabled) {
            flushStreams(); // send it all to the writer
            writer.stop(); // blocks waiting for it to finish
            dumpDefinitions();
        }
        for (auto &i : streams)
            delete i.second;
        streams.clear();
    }


    const string&
    SessionImpl::getName() const
    {return name;}
    

    Session&
    SessionImpl::getSession()
    {return *iface;}
    


    void
    SessionImpl::flushStreams()
    {
        Lock guard(streamsMutex);

        for (auto &i : streams) {
            if (i.second->pimpl->active)
                i.second->finish(); // make sure there is a closing event
            i.second->pimpl->flush();
        }
    }


    void
    SessionImpl::dumpDefinitions()
    {
        assert(enabled);
        string filename = name + ".kotf";
        std::ofstream defs(filename.c_str());
        
        defs << magic << "\n";

        defs << "startTime=" << 0 << "\n"
             << "finishTime=" << getTime() << "\n";

        defs << "# STREAMS\n";

        for (auto &i : streams)
            defs << "stream[" << i.second->pimpl->id << "]=\""
                 << i.second->getName() << "\"\n";

        defs << "# STATES\n";
        for (auto &i : stateById)
            defs << "state[" << i.first << "]=\"" << i.second.name << "\"\n";

        defs << "# COUNTERS\n";
        for (auto &i : counterById)
            defs << "counter[" << i.first << "]=\"" << i.second.name << "\"\n";

        defs << "# CHANNELS\n";
        for (auto &i : channelById)
            defs << "channel[" << i.first << "]=\"" << i.second.name << "\"\n";
    }




    void
    SessionImpl::enable()
    {
        if (!enabled) {
            enabled = true;
            writer.start();
        }
    }

    void
    SessionImpl::disable()
    {
        if (enabled) {
            enabled = false;
            writer.stop();
        }
    }

    bool
    SessionImpl::isEnabled() const
    {return enabled;}
    

    Stream&
    SessionImpl::getStream(const string& sname)
    {
        /*
        if (!enabled)
            throw NoStream
        */
        Lock guard(streamsMutex);
        auto i = streams.find(sname);
        if (i!=streams.end())
            return *i->second;
        uint64_t id = nextStreamId++;
        return *(streams[sname] = new Stream(*this->iface, id, sname));
    }


    State
    SessionImpl::getState(uint64_t st) throw (InvalidStateId)
    {
/*
        if (!enabled)
            return dummyState;
*/
        Lock guard(statesMutex);
        auto i = stateById.find(st);
        if (i == stateById.end())
            throw InvalidStateId(st);
        return i->second;
    }

    State
    SessionImpl::getState(const string& stname)
    {
        /*
          if (!enabled)
          return dummyState;
        */
        Lock guard(statesMutex);
        auto i = stateByName.find(stname);
        if (i != stateByName.end())
            return i->second;
        uint64_t newId = nextStateId++;
        return stateById[newId] = stateByName[stname] = State(newId, stname);
    }


    Counter
    SessionImpl::getCounter(uint64_t cnt) throw (InvalidCounterId)
    {
/*
        if (!enabled)
            return dummyCounter;
*/
        Lock guard(countersMutex);
        auto i = counterById.find(cnt);
        if (i == counterById.end())
            throw InvalidCounterId(cnt);
        return i->second;
    }

    Counter
    SessionImpl::getCounter(const string& cntname)
    {
        /*
          if (!enabled)
          return dummyState;
        */
        Lock guard(countersMutex);
        auto i = counterByName.find(cntname);
        if (i != counterByName.end())
            return i->second;
        uint64_t newId = nextCounterId++;
        return counterById[newId] = counterByName[cntname] = Counter(newId, cntname);
    }



    Channel
    SessionImpl::getChannel(uint64_t ch) throw (InvalidChannelId)
    {
/*
        if (!enabled)
            return dummyCounter;
*/
        Lock guard(channelsMutex);
        auto i = channelById.find(ch);
        if (i == channelById.end())
            throw InvalidChannelId(ch);
        return i->second;
    }

    Channel
    SessionImpl::getChannel(const string& chname)
    {
        /*
          if (!enabled)
          return dummyState;
        */
        Lock guard(channelsMutex);
        auto i = channelByName.find(chname);
        if (i != channelByName.end())
            return i->second;
        uint64_t newId = nextChannelId++;
        return channelById[newId] = channelByName[chname] = Channel(newId, chname);
    }


    uint64_t
    SessionImpl::getTime() const
    {
        return get_timestamp() - startTime;
    }


    EventBuffer*
    SessionImpl::getEventBuffer(StreamImpl* stream)
    {
        return writer.getAvailable(stream);
    }

    void
    SessionImpl::submit(EventBuffer* eb)
    {
        assert(eb);
        writer.pushPending(eb);
    }

}
